package com.example.http;

import com.example.http.annotations.Controller;
import com.example.http.annotations.RequestMapping;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.*;
import java.util.*;

public class Server {
//    缓存所有的控制器反射类
    static List<Class> controllers = new ArrayList<>();
//    声明Controller所归属的包名
    static String packageName = "com.example.http.controller";
//    通过url关联反射类对象并缓存
    static Map<String,Class> mappedClasses = new HashMap<>();
//    通过url关联反射类的实例并缓存
    static Map<String,Object> mappedInstance = new HashMap<>();
//    通过url关联反射类的对应函数并缓存
    static Map<String,Method> mappedMethods = new HashMap<>();

    /**
     * 根据包名找到该包下被Controller注解标记的类
     * @param packageName
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public  static void  initControllers(String packageName) throws IOException, ClassNotFoundException {
        //获取包下的文件
        Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(packageName.replace(".", "/"));
        while (dirs.hasMoreElements()){

            URL url = dirs.nextElement();
            //获取文件数组
            String[] file = new File(url.getFile()).list();
            //通过文件数量创建反射类数组
            Class[] classList = new Class[file.length];
            for (int i = 0; i < file.length; i++) {
                classList[i] = Class.forName(packageName + "." + file[i].replaceAll("\\.class", ""));
            }
            //找到Controller标记的类并保存到controllers中
            Arrays.stream(classList).forEach(classItem -> {
                Annotation res = classItem.getAnnotation(Controller.class);
               if(res!=null){
                   controllers.add(classItem);
               }
            });
        }
    }

    /**
     * 通过缓存的反射类找到类中标记RequestMapping的函数并组装URL与函数的对应关系
     * @param controllerClasses
     */
    public static void mapControllers(List<Class> controllerClasses){
        //遍历反射类
        controllerClasses.forEach(controllerClass -> {
            try {
//                获取注解对象
                RequestMapping requestMapping = (RequestMapping) controllerClass.getAnnotation(RequestMapping.class);
//                获取标记在类上的URL片段
                String topUrl = requestMapping.value();
//                获取反射类的所有函数
                Method[] methods = controllerClass.getDeclaredMethods();
                Arrays.stream(methods).forEach(method -> {
//                    获取标记了RequestMapping的函数对象
                    RequestMapping methodMapping = method.getAnnotation(RequestMapping.class);
                    if(method!=null){
//                        组装完整的访问路径
                        String fullUrl = topUrl+methodMapping.value();
//                        关联URL和反射类
                        mappedClasses.put(fullUrl,controllerClass);
//                        关联URL和对应的函数
                        mappedMethods.put(fullUrl,method);
//                        输出关联日志
                        System.out.println("mapped "+fullUrl+" to "+method+" with "+controllerClass);
                        try {
//                            关联URL和反射类的实例防止频繁访问的对象创建开销过大
                            Object obj = controllerClass.getDeclaredConstructor().newInstance();
                            mappedInstance.put(fullUrl,obj);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }

        });
    }

    /**
     * 启动服务函数
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
//        记录启动时间
        long begin = System.currentTimeMillis();
//        初始化控制器的反射类
        Server.initControllers(Server.packageName);
//        映射URL和对象函数
        Server.mapControllers(Server.controllers);
//        创建8000端口的HTTP服务
        HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
//        根据函数和URL的关系创建不同URL的HTTP监听器
        mappedMethods.forEach((url,method) -> {
            Object instance = mappedInstance.get(url);
            System.out.println("url:"+url);
            server.createContext(url, new RequestHandler(url,method,instance));
        });

        server.setExecutor(null); // creates a default executor
//        启动服务
        server.start();
//        输出启动时间
        System.out.println("server started in "+(System.currentTimeMillis()-begin)+"ms");
    }
//    http请求监听器，当用户访问Controller中定义的URL时就会触发该类的handle函数执行
    static class RequestHandler implements HttpHandler{
        String url;
        Method method;
        Object instance;
//        初始化HTTP监听器并记录关联函数的对象
        public RequestHandler(String url, Method method, Object instance){
            this.url = url;
            this.method = method;
            this.instance = instance;
        }
//        get参数转换器
        public Map<String,String> getQueryString(String qs){
            if(qs == null){
                return null;
            }
            Map<String ,String> queryMap = new HashMap<>();
            if(qs.contains("&")){
                String[] keyValueArr = qs.split("&");
                Arrays.stream(keyValueArr).forEach(keyValue -> {
                    String key = keyValue.split("=")[0];
                    String value = keyValue.split("=")[1];
                    queryMap.put(key,value);
                });
            }else{
                String key = qs.split("=")[0];
                String value = qs.split("=")[1];
                queryMap.put(key,value);
            }
            return queryMap;
        }
        @Override
        public void handle(HttpExchange t) throws IOException {
            try {
//              获取该请求所调用函数的参数信息
                Parameter[] params = this.method.getParameters();
//              创建装有参数的容器，在调用对应method时所传入的参数数组
                List<Object> paramList = new ArrayList<>();
//                获取请求路径
                URI uri = t.getRequestURI();
//                获取QueryString部分字符
                String qs = uri.getQuery();
//                System.out.println(qs);
//                将字符参数整理成Map对象
                Map<String, String> qsMap = getQueryString(qs);
//                System.out.println(qs);

                if(qsMap!=null){
                    //如果传递参数就将其匹配Controller中对应method的参数名并装载结果到参数数组
                    Arrays.stream(params).forEach(param -> {
                        paramList.add(qsMap.get(param.getName()));
                    });
                }else{
//                    如果没有传递任何参数则初始化空数组，防止反射调用函数不执行
                    Arrays.stream(params).forEach(item -> {
                        paramList.add(null);
                    });
                }
//                反射调用URL对应的函数
                String res = (String) this.method.invoke(this.instance,paramList.toArray());
//                设置返回类型
                t.getResponseHeaders().add("content-type","text/html;charset=utf-8");
//                发送响应体
                t.sendResponseHeaders(200, res.length());
//              写入返回数据
                OutputStream os = t.getResponseBody();
                os.write(res.getBytes());
                os.close();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

}
